// --------------------------------------------------------------------------------------------------------------------- 
// <copyright file="WorkerProcess.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the WorkerProcess type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------

namespace ScrumForTeamSystem.EventService.Services
{
    using System;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.Threading;

    using Infrastructure;
    using Infrastructure.Interfaces;

    using Properties;

    /// <summary>
    /// Defines the WorkerProcess type.
    /// </summary>
    internal class WorkerProcess : IWorkerProcess
    {
        /// <summary>
        /// The service name to use in exceptions.
        /// </summary>
        private const string ServiceName = "WorkerProcess";

        /// <summary>
        /// The Event Queue service instance
        /// </summary>
        private readonly IEventQueueService eventQueue;

        /// <summary>
        /// Milliseconds to sleep before dequing next event
        /// </summary>
        private readonly int sleepDuration;

        /// <summary>
        /// The event processor collection.
        /// </summary>
        private readonly Collection<ITfsEventProcessor> eventProcessors;

        /// <summary>
        /// Break process flag
        /// </summary>
        private bool processFlag;

        /// <summary>
        /// The background worker process thread
        /// </summary>
        private Thread workerThread;

        /// <summary>
        /// Initializes a new instance of the <see cref="WorkerProcess"/> class.
        /// </summary>
        /// <param name="eventQueueService">The ts event queue service.</param>
        public WorkerProcess(IEventQueueService eventQueueService)
        {
            this.eventQueue = eventQueueService;
            this.eventQueue.ObjectEnqueued += this.EventQueueObjectEnqueued;
            this.sleepDuration = Settings.Default.WorkerProcessPauseMilliseconds;
            this.eventProcessors = new Collection<ITfsEventProcessor>();
        }

        /// <summary>
        /// Occurs when [action] event raised.
        /// </summary>
        public event EventHandler<ActionEventArgs> Message;

        /// <summary>
        /// Occurs when [exception action] event raised.
        /// </summary>
        public event EventHandler<ExceptionActionEventArgs> ExceptionMessage;

        /// <summary>
        /// Gets a value indicating whether this instance is running.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is running; otherwise, <c>false</c>.
        /// </value>
        public bool IsRunning
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets a value indicating whether this instance is subscribed.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is subscribed; otherwise, <c>false</c>.
        /// </value>
        private bool IsSubscribed
        {
            get
            {
                return this.Message != null;
            }
        }

        /// <summary>
        /// Registers the event processor.
        /// </summary>
        /// <param name="eventProcessor">The event processor.</param>
        public void RegisterEventProcessor(ITfsEventProcessor eventProcessor)
        {
            if (eventProcessor == null)
            {
                throw new ArgumentNullException("eventProcessor");
            }

            this.eventProcessors.Add(eventProcessor);

            if (this.IsSubscribed)
            {
                this.Message(
                        this, new ActionEventArgs(string.Format(CultureInfo.InvariantCulture, Resources.String011, eventProcessor.GetType().FullName)));
            }
        }

        /// <summary>
        /// Starts the process.
        /// </summary>
        public void StartProcess()
        {
            if (this.IsRunning)
            {
                return;
            }

            this.processFlag = false;
            this.workerThread = new Thread(this.DoWork) { Name = Resources.String012 };
            this.workerThread.Start();

            if (this.IsSubscribed)
            {
                this.Message(this, new ActionEventArgs(Resources.String013));
            }
        }

        /// <summary>
        /// Stops the process.
        /// </summary>
        public void StopProcess()
        {
            this.processFlag = true;

            while (this.IsRunning)
            {
                Thread.Sleep(100);
            }

            if (this.IsSubscribed)
            {
                this.Message(this, new ActionEventArgs(Resources.String014));
            }
        }

        /// <summary>
        /// Does the work.
        /// </summary>
        private void DoWork()
        {
            if (this.IsSubscribed)
            {
                var actionEventArgs = new ActionEventArgs(
                        String.Format(
                            CultureInfo.CurrentUICulture, Resources.String015, Thread.CurrentThread.ManagedThreadId));

                this.Message(this, actionEventArgs);
            }

            this.IsRunning = true;

            while (this.eventQueue.HasQueuedEvents)
            {
                var tfsEvent = this.eventQueue.Dequeue();

                if (tfsEvent != null && tfsEvent.EventSource != null)
                {
                    foreach (var eventProcessor in this.eventProcessors)
                    {
                        try
                        {
                            try
                            {
                                eventProcessor.Process(tfsEvent);
                            }
                            catch (Exception ex)
                            {
                                var message = String.Format(CultureInfo.CurrentUICulture, Resources.String016, eventProcessor.GetType().FullName);

                                throw new ServiceException(ServiceName, message, ex);
                            }
                        }
                        catch (ServiceException serviceException)
                        {
                            if (this.IsSubscribed)
                            {
                                var actionEventArgs = new ExceptionActionEventArgs(serviceException);
                                this.ExceptionMessage(this, actionEventArgs);
                            }
                        }
                    }
                }

                if (this.processFlag)
                {
                    if (this.IsSubscribed)
                    {
                        var actionEventArgs = new ActionEventArgs(
                                String.Format(
                                    CultureInfo.CurrentUICulture,
                                    Resources.String017,
                                    Thread.CurrentThread.ManagedThreadId));

                        this.Message(this, actionEventArgs);
                    }

                    break;
                }

                Thread.Sleep(this.sleepDuration);
            }

            if (this.IsSubscribed)
            {
                var actionEventArgs = new ActionEventArgs(
                        String.Format(CultureInfo.CurrentUICulture, Resources.String018, Thread.CurrentThread.ManagedThreadId));

                this.Message(this, actionEventArgs);
            }

            this.IsRunning = false;

            this.StopProcess();
        }

        /// <summary>
        /// Handles the ObjectEnqueued event of the mEventQueue control.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event arguments.</param>
        private void EventQueueObjectEnqueued(object sender, EventArgs e)
        {
            this.StartProcess();
        }
    }
}